from block_types import *
from builder import *

from block_types import raw_blocks
from basic_templates import basic_templates
from templates_with_encounters import templates_with_encounters
from preset_rooms import *
from dungeons import *

base_types = load_block_types(raw_blocks)
templates = classify_templates(basic_templates,templates_with_encounters,base_types)
   
class Ready_dungeon:
  # members include:
  # levels - [Ready_level]
  # start - 5-tuple of level, r_down, r_right, down, right
  # entry_message - ''
  # exit_message - ''
  # ambient_messages - [Possible_message]
  pass

class Ready_level:
  # members include:
  # level_no (starting from 0, the top)
  # r_width
  # r_height
  # rooms - [[Ready_room]]
  # ambient_messages - [Possible_message]
  # keys_required_somewhere - e.g. ['$']
  pass

class Ready_tile_info:
  # type_abbrev - char (for now)
  # wall_type - W_ code
  # floor_type - F_ code
  # object_type - O_ code or None
  pass

class Ready_room:
  # members include:
  # level_no (starting from 0, the top)
  # r_down
  # r_right
  # width
  # height
  # grid - Grid
  # main_wall_type - W_ code
  # main_floor_type - F_ code
  # other_wall_types - Dict with key = (down,right), value = W_ code
  # other_floor_types - Dict with key = (down,right), value = F_ code
  # objects - Dict with key = (down,right), value = O_ code
  # signpost_messages - Dict with key = (down,right), value = string
  # exits_to - Dict with key = (down,right), value = a 2-tuple, with first element a D_ code and second ignored - unless first element is D_MAIN_DUNGEON, in which case second is a difficulty level
  # groups - [Ready_group]
  # ambient_messages - [Possible_message]
  # keys_required_somewhere - [O_ code]
  # gate_keys_required - [(O_ code,(down,right))]
  # door_keys_required - [(O_ code,dir)]
  def __init__(self,this_level,this_across,this_down):
    self.level = this_level # 0 is top, 1 is deeper
    self.r_right = this_across
    self.r_down = this_down
    self.width = 23
    self.height = 23
    self.grid = Grid(self.width,self.height,show_border='',fill_with='.')
    self.main_wall_type = W_BIGSTONE
    self.main_floor_type = F_ORDINARY_DARK # F_ORDINARY
    self.other_wall_types = {}
    self.other_floor_types = {}
    self.objects = {}
    self.signpost_messages = {}
    self.exits_to = {}
    self.groups = []
    self.ambient_messages = []
    self.keys_required_somewhere = []
    self.gate_keys_required = []
    self.door_keys_required = []
    
  def get_tile_info(self,down,right):
    # return a Ready_tile_info
    result = Ready_tile_info()
    result.wall_type = self.other_wall_types.get((down,right),self.main_wall_type) 
    result.floor_type = self.other_floor_types.get((down,right),self.main_floor_type) 
    result.object_type = self.objects.get((down,right),None)
    result.type_abbrev = self.grid.g[down][right]
    result.message = self.signpost_messages.get((down,right),'')
    result.exit_to = self.exits_to.get((down,right),None)
    return result
  
  def fill_with_preset_room(self,rm_code,inherited_properties):
    this_preset_room = preset_rooms[rm_code]
    properties = inherited_properties
    properties[TP_PARENT_FLOOR_TYPE] = self.main_floor_type
    properties[TP_PARENT_WALL_TYPE] = self.main_wall_type    
    offset = Grid_point(1,1)
    new_child_properties = this_preset_room.apply(self,self.grid,Grid_point(0,0),0,properties)
    self.fill_in_capital_letter_blocks(self.grid,properties,new_child_properties,Grid_point(0,0))
    for key_type in this_preset_room.keys_required_somewhere:
      self.keys_required_somewhere.append(key_type)
    
  def fill_with_templates(self,inherited_properties):
    offset = Grid_point(1,1)
    new_sockets = []
    for socket in self.grid.sockets:
      dir = self.grid.dir_into_grid(socket)
      door = socket.point_in_dir(dir)
      new_sockets.append(subtract(door,offset))
    grid_subset = Grid(21,21,definite_sockets=new_sockets)
    properties = inherited_properties
    properties[TP_PARENT_FLOOR_TYPE] = self.main_floor_type
    properties[TP_PARENT_WALL_TYPE] = self.main_wall_type
    filled = self.fill_area_with_random_template(grid_subset,offset,properties)
    if properties.get(TP_NO_ENCOUNTER,0):
      encounter_prob = 0
    else:
      encounter_prob = 0.5     
    if random.random()<encounter_prob:
      place_encounter(None,properties,self,grid_subset,offset,None,good=0,no_monsters=1)
    self.grid.blit(filled,offset,0)
    
  def fill_area_with_random_template(self,grid_subset,offset,child_properties):  
    tupleised_sockets = []
    for socket in grid_subset.sockets:
      tupleised_sockets.append(socket.to_tuple())
    tupleised_sockets.sort()
    tupleised_sockets = tuple(tupleised_sockets)
    width_height_nsockets = (grid_subset.width,grid_subset.height,len(grid_subset.sockets))
    if not (width_height_nsockets in templates) or not (tupleised_sockets in templates[width_height_nsockets]) or len(templates[width_height_nsockets][tupleised_sockets])==0:
      print "Nothing to insert!"
      print self.grid,'\n\n'
      print grid_subset,'\n\n'
      print offset,'\n\n'
      print grid_subset.sockets,'\n\n'
      grid_subset.fill('.')
      return grid_subset
    options = templates[width_height_nsockets][tupleised_sockets]
    if len(options)==1:
      choice = options[0]
    else:
      chosen = 0
      count = 0
      while not chosen and count<300:
        count = count + 1
        choice = random.choice(options) 
        if random.random()<choice[0].prob_to_insert(child_properties,choice[1]):
          chosen = 1
      if count==300:
        print "Nothing to insert! (2)"
        grid_subset.fill('.')
        print self.grid,'\n\n'
        print grid_subset,'\n\n'
        return grid_subset
    chosen_template = choice[0]
    rotation = choice[1]
    # CHOSEN THE TEMPLATE, NOW APPLY IT
    new_child_properties = chosen_template.apply(self,grid_subset,offset,rotation,child_properties)
    # NOW FILL IN CAPITAL-LETTER BLOCKS
    return self.fill_in_capital_letter_blocks(grid_subset,child_properties,new_child_properties,offset)
    
  def fill_in_capital_letter_blocks(self,grid_subset,child_properties,new_child_properties,offset):
    blocks_to_replace = [] # 4-tuple of top left corner (Grid_point), width, height, character
    for i in range(0,grid_subset.height):
      for j in range(0,grid_subset.width):
        cell_contents = grid_subset.get_cell(Grid_point(i,j)) 
        if cell_contents>="A" and cell_contents<="Z":
          already_in_list = 0
          for existing_block in blocks_to_replace:
            if i>=existing_block[0].down and i<existing_block[0].down+existing_block[2]:
              if j>=existing_block[0].right and j<existing_block[0].right+existing_block[1]:
                already_in_list = 1
                break
          if not already_in_list:
            new_block_width = 1
            new_block_height = 1
            while j+new_block_width<grid_subset.width:
              step_right = grid_subset.get_cell(Grid_point(i,j+new_block_width))
              if step_right==cell_contents:
                new_block_width = new_block_width + 1
              else:
                break
            while i+new_block_height<grid_subset.height:
              step_down = grid_subset.get_cell(Grid_point(i+new_block_height,j))
              if step_down==cell_contents:
                new_block_height = new_block_height + 1
              else:
                break         
            if new_block_width>=3 or new_block_height>=3:
              blocks_to_replace.append((Grid_point(i,j),new_block_width,new_block_height,cell_contents))
    for block in blocks_to_replace:
      new_sockets = []
      for i in range(block[0].down-1,block[0].down+block[2]+1):
        for j in (block[0].right-1,block[0].right+block[1]):
          is_socket = 0
          if i>=0 and i<grid_subset.height and j>=0 and j<grid_subset.width:
            if grid_subset.get_cell(Grid_point(i,j)) in ['.','+','D','$','R','G','B','S','<','>','{','}']:
              is_socket = 1
          else:
            if Grid_point(i,j) in grid_subset.sockets:
              is_socket = 1
          if is_socket:
            new_sockets.append(subtract(Grid_point(i,j),block[0]))
      for j in range(block[0].right,block[0].right+block[1]):
        for i in (block[0].down-1,block[0].down+block[2]):
          is_socket = 0
          if i>=0 and i<grid_subset.height and j>=0 and j<grid_subset.width:
            if grid_subset.get_cell(Grid_point(i,j)) in ['.','+','D','$','R','G','B','S','<','>','{','}']:
              is_socket = 1
          else:
            if Grid_point(i,j) in grid_subset.sockets:
              is_socket = 1
          if is_socket:
            new_sockets.append(subtract(Grid_point(i,j),block[0]))
      if block[3]=="M":
        new_grid = Grid(block[1],block[2],definite_sockets=new_sockets)
        wall_type = new_child_properties.get(TP_CONVERT,{}).get('#','#')
        floor_type = new_child_properties.get(TP_CONVERT,{}).get('.','.')
        new_grid.fill_with_maze(0.8,0,0.3,wall_type=wall_type,floor_type=floor_type)   
        grid_subset.blit(new_grid,block[0],0) 
      elif block[3]=="B":
        new_grid = Grid(block[1],block[2],definite_sockets=new_sockets)
        wall_type = new_child_properties.get(TP_CONVERT,{}).get('#','#')
        floor_type = new_child_properties.get(TP_CONVERT,{}).get('.','.')
        new_grid.stipple_with_blocks(0.25,wall_type=wall_type,floor_type=floor_type)
        grid_subset.blit(new_grid,block[0],0) 
      elif block[3]=="X":
        new_grid = Grid(block[1],block[2],definite_sockets=new_sockets)
        self.fill_area_with_random_template(new_grid,add(offset,block[0]),new_child_properties)
        grid_subset.blit(new_grid,block[0],0) 
      if block[3] in ("M","B"):
        if child_properties.get(TP_NO_ENCOUNTER,0):
          encounter_prob = 0
        else:
          encounter_prob = min(0.5,0.005*block[1]*block[2])     
        if random.random()<encounter_prob:
          group = Ready_group()
          group.attitude = A_HOSTILE
          group.monster_types = []
          group.locations = []
          place_encounter(None,new_child_properties,self,new_grid,add(offset,block[0]),group,good=0,no_monsters=0,no_terrains=0)
          if len(group.monster_types)>0:
            self.groups.append(group)
    return grid_subset
    
def get_ready_room(this_level,this_across,this_down,char,translation_table,exits,inherited_properties):
  # this_level==0 = top level, go down to L1
  # translation_table maps letters to RM_ codes
  result = Ready_room(this_level,this_across,this_down)
  for down in range(0,23):
    if down<>11:
      result.grid.g[down][0] = '#'
      result.grid.g[down][22] = '#'
  for right in range(0,23):
    if right<>11:
      result.grid.g[0][right] = '#'
      result.grid.g[22][right] = '#'
  result.grid.g[0][11] = exits[DIR_UP]
  result.grid.g[22][11] = exits[DIR_DOWN]
  result.grid.g[11][0] = exits[DIR_LEFT]
  result.grid.g[11][22] = exits[DIR_RIGHT]
  if exits[DIR_UP] <> '#': # TO DO - does this still hold?
    result.grid.add_definite_socket(Grid_point(0,11))
  if exits[DIR_DOWN] <> '#':
    result.grid.add_definite_socket(Grid_point(22,11))
  if exits[DIR_LEFT] <> '#':
    result.grid.add_definite_socket(Grid_point(11,0))
  if exits[DIR_RIGHT] <> '#':
    result.grid.add_definite_socket(Grid_point(11,22))
  if exits[DIR_UP] == '}':
    result.exits_to[(0,11)] = (D_TBC,-1)
  if exits[DIR_DOWN] == '}':
    result.exits_to[(22,11)] = (D_TBC,-1)
  if exits[DIR_LEFT] == '}':
    result.exits_to[(11,0)] = (D_TBC,-1)
  if exits[DIR_RIGHT] == '}':
    result.exits_to[(11,22)] = (D_TBC,-1)  
  result.main_floor_type = inherited_properties.get(TP_FLOOR_TYPE,result.main_floor_type)
  result.main_wall_type = inherited_properties.get(TP_WALL_TYPE,result.main_wall_type)
  if char=='#':
    result.grid.fill('#')
  elif (char>='a' and char<='z') or (char>='A' and char<='Z' and char<>'S' and char<>'E'):
    result.fill_with_preset_room(translation_table[char],inherited_properties)
  else:
    result.fill_with_templates(inherited_properties)   
  return result

def get_ready_level(this_level,levels,pre_ready_level,inherited_properties,ambient_messages,keys_required_somewhere = []):
  result = Ready_level()
  result.level = this_level # 0 is top, 1 is deeper
  rooms_across = pre_ready_level.pre_ready_room_grid.width
  rooms_down = pre_ready_level.pre_ready_room_grid.height
  result.r_width = rooms_across
  result.r_height = rooms_down
  result.ambient_messages = ambient_messages
  result.keys_required_somewhere = keys_required_somewhere # can be absolutely anywhere on the level
  result.rooms = []
  for this_down in range(rooms_down):
    row = []
    for this_across in range(rooms_across): 
      pre_ready_room = pre_ready_level.pre_ready_room_grid.get_cell(Grid_point(this_down,this_across))
      exits = {}
      for dir in [DIR_DOWN,DIR_RIGHT,DIR_UP,DIR_LEFT]:
        exits[dir] = pre_ready_room.exits.get(dir,'#')
      new_room = get_ready_room(this_level,this_across,this_down,pre_ready_room.char,pre_ready_room.translation_table,exits,inherited_properties)
      row.append(new_room)
      dump = open('mazes_'+str(this_level)+'_'+str(this_down)+'_'+str(this_across)+'.txt','w')
      dump.write(str(new_room.grid)+'\n\n')
      dump.close() 
    result.rooms.append(row)   
  return result

def get_ready_dungeon(dungeon_code,dungeon_level,character_level_on_entry,most_recent_main_dungeon_entry_difficulty,special_levels_already_visited):
  # pass dungeon_code, and if dungeon_code==D_MAIN_DUNGEON, pass dungeon_level also
  result = Ready_dungeon()
  if dungeon_code<>D_MAIN_DUNGEON:
    this_dungeon_info = dlib.d[dungeon_code]
  else:
    this_dungeon_info = get_main_dungeon_info(dungeon_level)
  result.ambient_messages = this_dungeon_info.ambient_messages
  result.entry_message = this_dungeon_info.entry_message
  result.exit_message = this_dungeon_info.exit_message
  all_levels_inherited_properties = this_dungeon_info.all_levels_inherited_properties
  floorplans = random.choice(this_dungeon_info.floorplans)
  translation_table = this_dungeon_info.translation_table
  result.levels = []
  n_levels = len(floorplans)
  fixed_door_types = [] # '$','S','R','G','B'
  random_door_locs = [] # 4-tuple of level, r_down, r_right, dir
  fixed_gate_types = [] # '\\','`','=','-','_' - be pretty careful with these, potential lock-out if they clash with fixed doors
  random_gate_locs = [] # 5-tuple of level, r_down, r_right, down, right
  for l in range(n_levels):
    this_floorplan = floorplans[l]
    pre_ready_level = Pre_ready_level(this_floorplan.long_string,this_floorplan.n_stairs_up,this_floorplan.n_stairs_down,this_floorplan.exits_to_add,this_floorplan.entrance_to_add,translation_table)
    for i in range(pre_ready_level.pre_ready_room_grid.height):
      for j in range(pre_ready_level.pre_ready_room_grid.width):
        this_room = pre_ready_level.pre_ready_room_grid.get_cell(Grid_point(i,j))
        for dir in [DIR_UP,DIR_DOWN,DIR_RIGHT,DIR_LEFT]:
          if this_room.exits[dir] in ['$','S','G','R','B']:
            fixed_door_types.append(this_room.exits[dir])
            print "placing a fixed door of type: ",this_room.exits[dir]
          elif this_room.exits[dir] == 'D' and dir in (DIR_RIGHT,DIR_DOWN): # don't want to do the left/up versions as well or we'd be placing both sides of the same door in different colors!!!
            random_door_locs.append((l,i,j,dir))
    this_level_inherited_properties = {}
    for key in all_levels_inherited_properties.keys():
      this_level_inherited_properties[key] = all_levels_inherited_properties[key]
    for key in this_floorplan.inherited_properties.keys():
      this_level_inherited_properties[key] = this_floorplan.inherited_properties[key]
    this_level_inherited_properties[TP_LEVEL_DIFFICULTY] = this_floorplan.difficulty
    result.levels.append(get_ready_level(l,n_levels,pre_ready_level,inherited_properties=this_level_inherited_properties,ambient_messages=this_floorplan.ambient_messages,keys_required_somewhere=this_floorplan.keys_required_somewhere))
    if pre_ready_level.enter_at_room is not None:
      if pre_ready_level.enter_at_dir_of_room==DIR_UP:
        result.start = (l,pre_ready_level.enter_at_room[0],pre_ready_level.enter_at_room[1],0,11)
      elif pre_ready_level.enter_at_dir_of_room==DIR_DOWN:
        result.start = (l,pre_ready_level.enter_at_room[0],pre_ready_level.enter_at_room[1],22,11)
      elif pre_ready_level.enter_at_dir_of_room==DIR_LEFT:
        result.start = (l,pre_ready_level.enter_at_room[0],pre_ready_level.enter_at_room[1],11,0)
      elif pre_ready_level.enter_at_dir_of_room==DIR_RIGHT:
        result.start = (l,pre_ready_level.enter_at_room[0],pre_ready_level.enter_at_room[1],11,22)
    this_ready_level = result.levels[len(result.levels)-1]
    for r_i in range(this_ready_level.r_height):
      for r_j in range(this_ready_level.r_width):
        room = this_ready_level.rooms[r_i][r_j]
        for i in range(room.grid.height):
          for j in range(room.grid.width):
            tile = room.grid.g[i][j]
            if tile == '|': # random gate 
              random_gate_locs.append((l,r_i,r_j,i,j))
            elif tile in ('\\','`','=','-','_'):
              fixed_gate_types.append(tile)
  # deal with random doors and gates
  use_door_types = []
  if not '$' in fixed_door_types and not '\\' in fixed_gate_types:
    use_door_types.append('$')
  if not 'S' in fixed_door_types and not '`' in fixed_gate_types:
    use_door_types.append('S')
  if not 'R' in fixed_door_types and not '=' in fixed_gate_types:
    use_door_types.append('R')
  if not 'G' in fixed_door_types and not '-' in fixed_gate_types:
    use_door_types.append('G')
  if not 'B' in fixed_door_types and not '_' in fixed_gate_types:
    use_door_types.append('B')
  if len(use_door_types) > len(random_door_locs):
    random.shuffle(use_door_types)
    use_door_types = use_door_types[0:len(random_door_locs)]
  else:
    while len(use_door_types) < len(random_door_locs):
      use_door_types.append('+')
    random.shuffle(use_door_types)
  count = 0
  for loc in random_door_locs:
    level = result.levels[loc[0]]
    room = level.rooms[loc[1]][loc[2]]
    print "placing a random door of type: ",use_door_types[count]
    if loc[3]==DIR_UP:
      room.grid.g[0][11] = use_door_types[count]
      adjacent_room = level.rooms[loc[1]-1][loc[2]]
      adjacent_room.grid.g[22][11] = use_door_types[count]
    elif loc[3]==DIR_DOWN:
      room.grid.g[22][11] = use_door_types[count]
      adjacent_room = level.rooms[loc[1]+1][loc[2]]
      adjacent_room.grid.g[0][11] = use_door_types[count]    
    elif loc[3]==DIR_RIGHT:
      room.grid.g[11][22] = use_door_types[count]
      adjacent_room = level.rooms[loc[1]][loc[2]+1]
      adjacent_room.grid.g[11][0] = use_door_types[count]      
    elif loc[3]==DIR_LEFT:
      room.grid.g[11][0] = use_door_types[count]
      adjacent_room = level.rooms[loc[1]][loc[2]-1]
      adjacent_room.grid.g[11][22] = use_door_types[count]
    room.door_keys_required.append((use_door_types[count],loc[3]))
    count = count + 1
  available_gate_types = []
  if not '$' in fixed_door_types and not '\\' in fixed_gate_types and not '$' in use_door_types:
    available_gate_types.append('\\')
  if not 'S' in fixed_door_types and not '`' in fixed_gate_types and not 'S' in use_door_types:
    available_gate_types.append('`')
  if not 'R' in fixed_door_types and not '=' in fixed_gate_types and not 'R' in use_door_types:
    available_gate_types.append('=')
  if not 'G' in fixed_door_types and not '-' in fixed_gate_types and not 'G' in use_door_types:
    available_gate_types.append('-')
  if not 'B' in fixed_door_types and not '_' in fixed_gate_types and not 'B' in use_door_types:
    available_gate_types.append('_')
  if len(available_gate_types)==0:
    available_gate_types.append('[')
  gates_to_use = []
  while len(gates_to_use) < len(random_gate_locs):
    gates_to_use.append(random.choice(available_gate_types))                         
  count = 0
  for loc in random_gate_locs:
    level = result.levels[loc[0]]
    room = level.rooms[loc[1]][loc[2]]    
    print "placing a random gate of type: ",gates_to_use[count]," at, ",loc
    room.grid.g[loc[3]][loc[4]] = gates_to_use[count]
    room.gate_keys_required.append((gates_to_use[count],(loc[3],loc[4])))
    count = count + 1
  # designate TBC exits
  tbc_exit_locs = [] # 5-tuple of level, r_down, r_right, down, right
  for l in range(n_levels):
    this_ready_level = result.levels[l]
    for i in range(this_ready_level.r_height):
      for j in range(this_ready_level.r_width):
        this_ready_room = this_ready_level.rooms[i][j]
        for down_right in [(11,22),(11,0),(22,11),(0,11)]:
          if down_right in this_ready_room.exits_to:
            this_exit_to = this_ready_room.exits_to[down_right]
            if this_exit_to is not None and this_exit_to[0]==D_TBC:
              tbc_exit_locs.append((l,i,j,down_right[0],down_right[1]))
  confirmed_exits_to = []
  if dungeon_code==D_EASY_DUNGEON:
    for tbc_exit_loc in tbc_exit_locs:
      main_dungeon_level = 2
      confirmed_exits_to.append((D_MAIN_DUNGEON,main_dungeon_level))
  elif dungeon_code<>D_MAIN_DUNGEON:
    max_dungeon_difficulty_level = 2
    for floorplan in floorplans:
      max_dungeon_difficulty_level = max(max_dungeon_difficulty_level,floorplan.difficulty)
    for tbc_exit_loc in tbc_exit_locs:
      main_dungeon_level = max(2,min(most_recent_main_dungeon_entry_difficulty+1,max(appropriate_difficulty_level_for_character_level(character_level_on_entry)-1,max_dungeon_difficulty_level)))
      confirmed_exits_to.append((D_MAIN_DUNGEON,main_dungeon_level))
  elif dungeon_code==D_MAIN_DUNGEON:
    min_dungeon_difficulty_level = 1000
    max_dungeon_difficulty_level = 2
    for floorplan in floorplans:
      min_dungeon_difficulty_level = min(min_dungeon_difficulty_level,floorplan.difficulty)
      max_dungeon_difficulty_level = max(max_dungeon_difficulty_level,floorplan.difficulty)
    special_levels_picked = []
    for tbc_exit_loc in tbc_exit_locs[:-1]:
      possible_special_levels = [D_CAVE,D_CRYPT,D_EASY_DUNGEON,D_FIERY_PITS,D_GOBLIN_CAVE,D_HALL_OF_HEROES,D_MAZE,D_QUIET_DUNGEON,D_TREASURE_ROOM,D_TREASURE_ROOM_2]
      if D_DROW in special_levels_already_visited:
        possible_special_levels.append(D_CROSSROADS)
        possible_special_levels.append(D_FIREDRAKE)
        possible_special_levels.append(D_HALL_OF_CHAMPIONS)
      else:
        possible_special_levels.append(D_DROW)
      available_special_levels = []
      for special_level in possible_special_levels:
        if not (special_level in special_levels_picked) and (dlib.d[special_level].can_be_revisited or not (special_level in special_levels_already_visited)):
          if dlib.d[special_level].indicative_difficulty <= max_dungeon_difficulty_level+1:
            available_special_levels.append(special_level)
      if random.random()<0.5 and len(available_special_levels)>0:
        special_level_picked = random.choice(available_special_levels)
        confirmed_exits_to.append((special_level_picked,min(max_dungeon_difficulty_level,appropriate_difficulty_level_for_character_level(character_level_on_entry)-1)))
        special_levels_picked.append(special_level_picked)
      else:
        if floorplans[tbc_exit_loc[0]].difficulty < max_dungeon_difficulty_level:
          main_dungeon_level = max(floorplans[tbc_exit_loc[0]].difficulty,appropriate_difficulty_level_for_character_level(character_level_on_entry)-1)
        elif floorplans[tbc_exit_loc[0]].difficulty > min_dungeon_difficulty_level:
          main_dungeon_level = floorplans[tbc_exit_loc[0]].difficulty
        else:
          main_dungeon_level = max(floorplans[tbc_exit_loc[0]].difficulty,appropriate_difficulty_level_for_character_level(character_level_on_entry)-1)
        confirmed_exits_to.append((D_MAIN_DUNGEON,main_dungeon_level))
    tbc_exit_loc = tbc_exit_locs[len(tbc_exit_locs)-1]
    if D_DROW in special_levels_already_visited:
      if random.random()<0.5 and not D_CROSSROADS in special_levels_already_visited and max_dungeon_difficulty_level>=5:
        confirmed_exits_to.append((D_CROSSROADS,0))
      elif random.random()<0.5 and max_dungeon_difficulty_level>=5:
        confirmed_exits_to.append((D_FIREDRAKE,0))
      else:
        main_dungeon_level = max(floorplans[tbc_exit_loc[0]].difficulty,appropriate_difficulty_level_for_character_level(character_level_on_entry)-1)
        confirmed_exits_to.append((D_MAIN_DUNGEON,main_dungeon_level))
    else:
      if appropriate_difficulty_level_for_character_level(character_level_on_entry)>=5:
        confirmed_exits_to.append((D_DROW,0))
      else:
        main_dungeon_level = max(floorplans[tbc_exit_loc[0]].difficulty,appropriate_difficulty_level_for_character_level(character_level_on_entry)-1)
        confirmed_exits_to.append((D_MAIN_DUNGEON,main_dungeon_level))
  for i in range(len(confirmed_exits_to)):
    confirmed_exit_to = confirmed_exits_to[i]
    tbc_exit_loc = tbc_exit_locs[i]
    print "placing exit of type: ",confirmed_exit_to," at ",tbc_exit_loc
    result.levels[tbc_exit_loc[0]].rooms[tbc_exit_loc[1]][tbc_exit_loc[2]].exits_to[(tbc_exit_loc[3],tbc_exit_loc[4])] = confirmed_exit_to
  return result

def appropriate_difficulty_level_for_character_level(clvl):
  if clvl<=3:
    return 2
  elif clvl<=5:
    return 3
  elif clvl<=7:
    return 4
  elif clvl<=9:
    return 5
  elif clvl<=11:
    return 6
  else:
    return 7
  
class Pre_ready_room:
  def __init__(self,char,translation_table):
    self.char = char 
    self.translation_table = translation_table # key is small letter, value is RM_ code
    self.exits = {} # key is DIR_ code, value is '<','>','+'...
    self.exits[DIR_DOWN] = '#'
    self.exits[DIR_UP] = '#'
    self.exits[DIR_RIGHT] = '#'
    self.exits[DIR_LEFT] = '#'
    if (self.char>='a' and self.char<='z') or (self.char>='A' and self.char<='Z' and char<>'S' and char<>'E'):
      self.exits_fixed = 1 # do not create,destroy or modify them!
      room_template = preset_rooms[translation_table[char]]
      for dir in [DIR_DOWN,DIR_UP,DIR_RIGHT,DIR_LEFT]:
        self.exits[dir] = room_template.exit_types[dir]
    else:
      self.exits_fixed = 0
    self.already_connected_up = 0
    self.no_more_doors_to_add = 0
    
class Pre_ready_level:
  def __init__(self,long_string,n_stairs_up,n_stairs_down,exits_to_add,entrance_to_add,translation_table):
    # pass a list of strings representing the level
    # so far can be #, . (random room), < or > (random room with guaranteed stairs), S (start with entrance), E (exit)
    # or a small letter, in which case look it up in translation_table - dict from small letters to RM_ codes.
    # Make sure to surround with a border of #s
    # SELF NOTE: Preset rooms must have the doors in their description; and no other doors into them exist.
    # Make sure one can get into them; and also make sure they aren't placed such that they
    #  have a door opening onto a # room.
    buffer = text_block_to_list_of_strings(long_string)
    self.pre_ready_room_grid = Grid(len(buffer[0])-2,len(buffer)-2)
    self.enter_at_room = None # (r_down,r_right)
    self.enter_at_dir_of_room = None # DIR_ code
    stairs_up_placed = 0
    stairs_down_placed = 0
    # fill in the features supplied
    for i in range(self.pre_ready_room_grid.height):
      for j in range(self.pre_ready_room_grid.width):
        this_char = buffer[i+1][j+1]
        this_room = Pre_ready_room(this_char,translation_table)
        this_char_translated = translation_table.get(this_char,None)
        exterior_wall_dirs = []
        if i==0:
          exterior_wall_dirs.append(DIR_UP)
        elif i==self.pre_ready_room_grid.height-1:
          exterior_wall_dirs.append(DIR_DOWN)
        if j==0:
          exterior_wall_dirs.append(DIR_LEFT)
        elif j==self.pre_ready_room_grid.width-1:
          exterior_wall_dirs.append(DIR_RIGHT)
        if len(exterior_wall_dirs)>0:
          if this_char=='>':
            # print "stairs down requested ", i, " ", j
            this_room.exits[exterior_wall_dirs[0]]='>'
            stairs_down_placed = stairs_down_placed + 1
          elif this_char=='<':
            # print "stairs up requested ", i, " ", j
            this_room.exits[exterior_wall_dirs[0]]='<'
            stairs_up_placed = stairs_up_placed + 1
          elif this_char=='S':
            this_room.exits[exterior_wall_dirs[0]]='{'
            self.enter_at_room = (i,j)
            self.enter_at_dir_of_room = exterior_wall_dirs[0]
          elif this_char=='E':
            this_room.exits[exterior_wall_dirs[0]]='}'
          elif (this_char>='a' and this_char<='z') or (this_char>='A' and this_char<='Z' and this_char<>'S' and this_char<>'E'):
            is_entrance = preset_rooms[this_char_translated].entrance
            entrance_placed = 0
            for dir in [DIR_RIGHT,DIR_LEFT,DIR_UP,DIR_DOWN]:
              exit_type = preset_rooms[this_char_translated].exit_types[dir]
              this_room.exits[dir] = exit_type
              if exit_type==">":
                # print "stairs down placed in dir ", dir, " of ", i, " ", j
                stairs_down_placed = stairs_down_placed + 1
              elif exit_type=="<":
                # print "stairs up placed in dir ", dir, " of ", i, " ", j
                stairs_up_placed = stairs_up_placed + 1
              elif exit_type=='{':
                self.enter_at_room = (i,j)
                self.enter_at_dir_of_room = dir
                entrance_placed = 1
            if is_entrance and not entrance_placed: # special case, put it on an exit (only happens at the very start of the game)
              for dir in [DIR_RIGHT,DIR_LEFT,DIR_UP,DIR_DOWN]:
                exit_type = preset_rooms[this_char_translated].exit_types[dir]
                if exit_type<>'#' and exit_type<>'+':
                  self.enter_at_room = (i,j)
                  self.enter_at_dir_of_room = dir
        self.pre_ready_room_grid.set_cell(Grid_point(i,j),this_room)
    # put in opposite sides of doors in preset rooms 
    for i in range(self.pre_ready_room_grid.height):
      for j in range(self.pre_ready_room_grid.width):
        this_char = buffer[i+1][j+1]
        if (this_char>='a' and this_char<='z') or (this_char>='A' and this_char<='Z' and this_char<>'S' and this_char<>'E'):
          this_char_translated = translation_table.get(this_char,None)
          for dir in [DIR_DOWN,DIR_UP,DIR_RIGHT,DIR_LEFT]:
            exit_type = preset_rooms[this_char_translated].exit_types[dir]
            if exit_type in ['+','D','$','S','R','G','B']: 
              exit_to_room = self.pre_ready_room_grid.get_cell(Grid_point(i,j).point_in_dir(dir))
              exit_to_room.exits[opposite(dir)] = exit_type
    # now put in any extra required stairs
    for i in range(n_stairs_up):
      successfully_placed = 0
      for j in range(20000):
        r_down = random.randint(0,self.pre_ready_room_grid.height-1)
        r_right = random.randint(0,self.pre_ready_room_grid.width-1)
        this_room = self.pre_ready_room_grid.get_cell(Grid_point(r_down,r_right))
        if this_room.char == '.':
          dir = random.choice([DIR_DOWN,DIR_RIGHT,DIR_LEFT,DIR_UP])
          this_exit = this_room.exits[dir]
          if this_exit=='#' and not (this_room.exits[DIR_DOWN]=='<' or this_room.exits[DIR_UP]=='<' or this_room.exits[DIR_LEFT]=='<' or this_room.exits[DIR_RIGHT]=='<'):
            point_exited_to = Grid_point(r_down,r_right).point_in_dir(dir)
            if point_exited_to.down<0 or point_exited_to.down==self.pre_ready_room_grid.height or point_exited_to.right<0 or point_exited_to.right==self.pre_ready_room_grid.width:
              this_room.exits[dir] = '<'
              successfully_placed = 1
              print "stairs up added in dir ", dir, " of ", r_down, " ", r_right
              break
            if j>=50:
              print r_down,r_right,dir,self.pre_ready_room_grid.get_cell(point_exited_to)
            if j>=50 and self.pre_ready_room_grid.get_cell(point_exited_to).char=='#':
              this_room.exits[dir] = '<'
              successfully_placed = 1
              print "stairs up added in dir ", dir, " of ", r_down, " ", r_right
              break
      if not successfully_placed:
        print long_string
        print "Failed to place stair!"
        raise SystemError
    for i in range(n_stairs_down):
      successfully_placed = 0
      for j in range(20000):
        r_down = random.randint(0,self.pre_ready_room_grid.height-1)
        r_right = random.randint(0,self.pre_ready_room_grid.width-1)
        this_room = self.pre_ready_room_grid.get_cell(Grid_point(r_down,r_right))
        if this_room.char == '.':
          dir = random.choice([DIR_DOWN,DIR_RIGHT,DIR_LEFT,DIR_UP])
          this_exit = this_room.exits[dir]
          if this_exit=='#' and not (this_room.exits[DIR_DOWN]=='>' or this_room.exits[DIR_UP]=='>' or this_room.exits[DIR_LEFT]=='>' or this_room.exits[DIR_RIGHT]=='>'):
            point_exited_to = Grid_point(r_down,r_right).point_in_dir(dir)
            if point_exited_to.down<0 or point_exited_to.down==self.pre_ready_room_grid.height or point_exited_to.right<0 or point_exited_to.right==self.pre_ready_room_grid.width:
              this_room.exits[dir] = '>'
              successfully_placed = 1
              print "stairs down added in dir ", dir, " of ", r_down, " ", r_right
              break
            if j>=50:
              print r_down,r_right,dir,self.pre_ready_room_grid.get_cell(point_exited_to)
            if j>=50 and self.pre_ready_room_grid.get_cell(point_exited_to).char=='#':
              this_room.exits[dir] = '>'
              successfully_placed = 1
              print "stairs down added in dir ", dir, " of ", r_down, " ", r_right
              break
      if not successfully_placed:
        print long_string
        print "Failed to place stair!"
        raise SystemError
    # and randomly place exits
    for i in range(exits_to_add):
      successfully_placed = 0
      for j in range(20000):
        r_down = random.randint(0,self.pre_ready_room_grid.height-1)
        r_right = random.randint(0,self.pre_ready_room_grid.width-1)
        this_room = self.pre_ready_room_grid.get_cell(Grid_point(r_down,r_right))
        if this_room.char == '.':
          dir = random.choice([DIR_DOWN,DIR_RIGHT,DIR_LEFT,DIR_UP])
          this_exit = this_room.exits[dir]
          if this_exit=='#' and not (this_room.exits[DIR_DOWN]=='}' or this_room.exits[DIR_UP]=='}' or this_room.exits[DIR_LEFT]=='}' or this_room.exits[DIR_RIGHT]=='}'):
            point_exited_to = Grid_point(r_down,r_right).point_in_dir(dir)
            if point_exited_to.down<0 or point_exited_to.down==self.pre_ready_room_grid.height or point_exited_to.right<0 or point_exited_to.right==self.pre_ready_room_grid.width:
              this_room.exits[dir] = '}'
              successfully_placed = 1
              print "exit added in dir ", dir, " of ", r_down, " ", r_right
              break
            if j>=50 and self.pre_ready_room_grid.get_cell(point_exited_to).char=='#':
              this_room.exits[dir] = '}'
              successfully_placed = 1
              print "exit added in dir ", dir, " of ", r_down, " ", r_right
              break
      if not successfully_placed:
        print "Failed to place exit!"
        raise SystemError
    if entrance_to_add:
      successfully_placed = 0
      for j in range(20000):
        r_down = random.randint(0,self.pre_ready_room_grid.height-1)
        r_right = random.randint(0,self.pre_ready_room_grid.width-1)
        this_room = self.pre_ready_room_grid.get_cell(Grid_point(r_down,r_right))
        if this_room.char == '.':
          dir = random.choice([DIR_DOWN,DIR_RIGHT,DIR_LEFT,DIR_UP])
          this_exit = this_room.exits[dir]
          if this_exit=='#':
            point_exited_to = Grid_point(r_down,r_right).point_in_dir(dir)
            if point_exited_to.down<0 or point_exited_to.down==self.pre_ready_room_grid.height or point_exited_to.right<0 or point_exited_to.right==self.pre_ready_room_grid.width:
              this_room.exits[dir] = '{'
              successfully_placed = 1
              print "entrance added in dir ", dir, " of ", r_down, " ", r_right
              self.enter_at_room = (r_down,r_right)
              self.enter_at_dir_of_room = dir
              break
            if j>=50 and self.pre_ready_room_grid.get_cell(point_exited_to).char=='#':
              this_room.exits[dir] = '{'
              successfully_placed = 1
              print "entrance added in dir ", dir, " of ", r_down, " ", r_right
              self.enter_at_room = (r_down,r_right)
              self.enter_at_dir_of_room = dir
              break
      if not successfully_placed:
        print "Failed to place entrance!"
        raise SystemError      
    # Create doors to turn every potentially connected set of rooms into a connected network, and then a few more besides.
    # Self note - do not remove any existing doors here!
    # Another self note - do not add exits to a Pre_ready_room with exits_fixed = 1!
    for i in range(self.pre_ready_room_grid.height):
      for j in range(self.pre_ready_room_grid.width):
        this_room = self.pre_ready_room_grid.get_cell(Grid_point(i,j))
        if this_room.char<>'#' and not this_room.already_connected_up:
          if this_room.exits[DIR_UP]<>'#' or this_room.exits[DIR_DOWN]<>'#' or this_room.exits[DIR_LEFT]<>'#' or this_room.exits[DIR_RIGHT]<>'#':
            # this room is one of a block of orthogonally adjacent non-# rooms that has not been mutually linked yet
            # link them! and mark them all with already_connected_up=1 as you do so.
            this_room.already_connected_up = 1
            # so first find all members of the aforementioned block
            rooms_to_connect = []
            rooms_to_add = [Grid_point(i,j)]
            while len(rooms_to_add)>0:
              new_rooms_to_add = []
              for room in rooms_to_add:
                for dir in [DIR_LEFT,DIR_RIGHT,DIR_UP,DIR_DOWN]:
                  new_room = room.point_in_dir(dir)
                  if new_room.down>=0 and new_room.down<self.pre_ready_room_grid.height and new_room.right>=0 and new_room.right<self.pre_ready_room_grid.width:
                    if not (new_room in new_rooms_to_add) and not (new_room in rooms_to_connect):
                      if not self.pre_ready_room_grid.get_cell(new_room).char=='#':
                        new_rooms_to_add.append(new_room)
                        rooms_to_connect.append(new_room)
              rooms_to_add = new_rooms_to_add
            # and then, uh... connect them!
            rooms_connected = [Grid_point(i,j)]
            while len(rooms_connected)<len(rooms_to_connect):
              point_to_connect_from = random.choice(rooms_connected)
              room_to_connect_from = self.pre_ready_room_grid.get_cell(point_to_connect_from)
              if not room_to_connect_from.no_more_doors_to_add:
                options = []
                for dir in [DIR_DOWN,DIR_RIGHT,DIR_LEFT,DIR_UP]:
                  print point_to_connect_from,dir,room_to_connect_from.exits[dir]
                  if room_to_connect_from.exits[dir]=='#' and not room_to_connect_from.exits_fixed:
                    point_to_connect_to = point_to_connect_from.point_in_dir(dir)
                    if point_to_connect_to in rooms_to_connect and not point_to_connect_to in rooms_connected:
                      room_to_connect_to = self.pre_ready_room_grid.get_cell(point_to_connect_to)
                      if not room_to_connect_to.exits_fixed:
                        options.append(dir)
                  elif room_to_connect_from.exits[dir] in ['+','D','$','S','R','G','B']:
                    point_to_connect_to = point_to_connect_from.point_in_dir(dir)
                    room_to_connect_to = self.pre_ready_room_grid.get_cell(point_to_connect_to)
                    room_to_connect_to.already_connected_up = 1
                    if not point_to_connect_to in rooms_connected:
                      rooms_connected.append(point_to_connect_to)      
                if len(options)==0:
                  room_to_connect_from.no_more_doors_to_add = 1
                  continue
                dir = random.choice(options)
                point_to_connect_to = point_to_connect_from.point_in_dir(dir)
                room_to_connect_to = self.pre_ready_room_grid.get_cell(point_to_connect_to)
                room_to_connect_from.exits[dir] = '+'
                room_to_connect_to.exits[opposite(dir)] = '+'
                room_to_connect_to.already_connected_up = 1
                rooms_connected.append(point_to_connect_to)
    # and add a few more doors to be sure
    door_constant = 0.1
    for i in range(self.pre_ready_room_grid.height):
      for j in range(self.pre_ready_room_grid.width):
        this_room = self.pre_ready_room_grid.get_cell(Grid_point(i,j))
        if this_room.char<>'#' and not this_room.exits_fixed:
          for dir in [DIR_DOWN,DIR_RIGHT,DIR_LEFT,DIR_UP]:
            if random.random()<door_constant:
              if this_room.exits[dir]=='#':
                next_grid_point = Grid_point(i,j).point_in_dir(dir)
                if next_grid_point.down>=0 and next_grid_point.down<self.pre_ready_room_grid.height and next_grid_point.right>=0 and next_grid_point.right<self.pre_ready_room_grid.width:
                  next_room = self.pre_ready_room_grid.get_cell(next_grid_point)
                  if next_room.char<>'#' and not next_room.exits_fixed:
                    # crikey! let's do it
                    this_room.exits[dir] = '+'
                    next_room.exits[opposite(dir)] = '+'

psyco.bind(get_ready_room)
psyco.bind(Ready_room.get_tile_info)
psyco.bind(Ready_room.fill_with_templates)
psyco.bind(Ready_room.fill_area_with_random_template)
psyco.bind(Pre_ready_level.__init__)
  